package com.itcm.springsecuritydemo.config;

import com.itcm.springsecuritydemo.handler.MyAccessDeniedHandler;
import com.itcm.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.itcm.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import com.itcm.springsecuritydemo.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @author jinchengming
 * @version 1.0.0
 * @date 2022/10/17 15:41
 */

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin()
                // 当请求为/login时，执行UserDetailsServiceImpl，即执行自定义登录逻辑，这里地址接登录的时候，表单中配置的提交表单信息的地址
                .loginProcessingUrl("/login")
                // 自定义登录页面
                .loginPage("/login.html")
                // 登录成功后请求地址，必须是Post请求，需要提供一个toIndex的post接口，重定向到index.html，这里不配置默认成功页面就是index.html
                // .successForwardUrl("toIndex")
                // 登录失败后请求，同上，也必须是Post,这个斜杠不能少
                // .failureForwardUrl("/toError")
                // 配置自定义成功失败处理器，不能和成功失败跳转共存
                .successHandler(new MyAuthenticationSuccessHandler("/test.html"))
                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

        // 授权认证
        http.authorizeRequests()
                // 配置登录页面请求放行
                // ant表达式： ? 匹配一个字符   * 匹配0个或多个字符  ** 匹配0个或多个目录
                // 支持在第一个参数指定请求方式，如：.antMatchers(HttpMethod.GET, "/login.html","/error.html").permitAll()
                .antMatchers("/login.html","/error.html","/showLogin").permitAll()
                // 也支持通过正则表达式匹配  .regexMatchers
                // mvcMatchers("/test").servletPath("/api")  支持配servletPath 不常用

                // 配置权限  配置包含任何一个权限，参数可多个： .hasAnyAuthority("a","b")
                .antMatchers("/test.html").hasAuthority("admin")
                // 不同于上面的配置，这里有个约束，角色名前缀为ROLE_ 这里配置的时候就不需要配置前缀了，同样可以配置.hasAnyRole(...)
                .antMatchers("/test1").hasRole("abc")
                // 支持匹配ip
                .antMatchers("test2").hasIpAddress("127.0.0.1")

                // access 使用 上面所有的权限控制方法底层都是access,如果直接使用也很简单，传入对应控制的属性名即可
                .antMatchers("/test3.html").access("hasRole('abc')")

                // access 结合自定义方法控制权限
//                .anyRequest().access("@myAuthServiceImpl.hasPermission(authentication)");

                // 所有请求都必须认证，即登录之后才能访问
                .anyRequest().authenticated();

        // 关闭csrf防护
//        http.csrf().disable();

        // 异常处理
//        http.exceptionHandling()
//                .accessDeniedHandler(myAccessDeniedHandler);

        // 记住我
        http.rememberMe()
                // 自定义登录逻辑
                .userDetailsService(userDetailsService)
                // 持久层对象
                .tokenRepository(persistentTokenRepository);

        http.logout()
                .logoutSuccessUrl("/login.html");
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 第一次启动时自动建表，仅测试用，测试完注释，否则再次启动报错
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}
